自定义地图服务显示 Sample详情

最后更新时间:2019年7月5日

地图服务负责给地图提供数据,MapGIS Mobile支持自定义地图服务的功能,包括自定义的矢量、瓦片地图服务。可以在地图视图中加载自定义数据、自定义切片规则的地图,具备良好的扩展性。

在实现自定义地图服务之前,需要理解地图服务的概念。地图服务,即是负责提供地图数据的服务,它为服务图层ServerLayer提供地图数据,在其中包括地图数据范围、数据类型、服务名称、地图参照系等信息。有了地图服务,才能获取地图数据进而显示出来。

实现方法

地图服务对应MGSMapServer类,其子类包括矢量地图服务:MGSVectorMapServer、和瓦片地图服务:MGSTileMapServer,对应GIS地理数据中的两种常用类型。服务图层MGSServerLayer调用MGSMapServer来获取数据,然后将MGSServerLayer添加到MGSMap地图中,然后将地图赋予地图容器MGSMapView即可显示地图。

实现自定义的地图服务,就需要自定义一个地图服务类,继承MGSMapServer的子类MGSVectorMapServer或者MGSTileMapServer,然后重写实现其中的方法。

自定义矢量地图服务

自定义矢量地图服务,能够将普通的图片赋予地理空间信息,从而以地图的形式展示,可用于在基础底图上添加额外的信息。

1

自定义矢量地图服务

自定义矢量地图服务CustomVectorMapServer,继承MGSVectorMapServer,重写方法,最关键的方法是-getVectorImageWithWidth:height:dispRect:,传递图片的高、宽、要显示的地图范围,经过一系列运算,生成显示的地图数据。

/*!
 *	@brief	获取显示范围内的图像数据
 *	@param 	width	图像的宽(像素)
 *	@param 	height	图像的高(像素)
 *	@param 	dispRect	显示范围(逻辑范围)
 *	@return	图像数据
 */
-(NSData*)getVectorImageWithWidth:(long) width height:(long) height dispRect:(MGSRect) dispRect
{
    if (dispRect.xmax < _dataRect.xmin ||dispRect.xmin > _dataRect.xmax || dispRect.ymax < _dataRect.ymin || dispRect.ymin > _dataRect.ymax) {
        return nil;
    }
    
    double scale = (dispRect.xmax - dispRect.xmin)/(_dataRect.xmax - _dataRect.xmin);
    MGSRect tempRect;
    tempRect.xmin = (_dataRect.xmin > dispRect.xmin) ? _dataRect.xmin : dispRect.xmin;
    tempRect.xmax = (_dataRect.xmax < dispRect.xmax) ? _dataRect.xmax : dispRect.xmax;
    tempRect.ymin = (_dataRect.ymin > dispRect.ymin) ? _dataRect.ymin : dispRect.ymin;
    tempRect.ymax = (_dataRect.ymax < dispRect.ymax) ? _dataRect.ymax : dispRect.ymax;
    
    MGSRect pixRect;    
    pixRect.xmin = 640 * (tempRect.xmin - _dataRect.xmin)/(_dataRect.xmax - _dataRect.xmin);
    pixRect.xmax = 640 * (tempRect.xmax - _dataRect.xmin)/(_dataRect.xmax - _dataRect.xmin);
    pixRect.ymin = 591 * (_dataRect.ymax - tempRect.ymax)/(_dataRect.ymax - _dataRect.ymin);
    pixRect.ymax = 591 * (_dataRect.ymax - tempRect.ymin)/(_dataRect.ymax - _dataRect.ymin);
    
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(640 * scale, 591 * scale), NO, 1);
    [self.myImage drawInRect:CGRectMake(0, 0, 640 * scale , 591 * scale)];
    
    //图片缩放显示比比值倍数
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    //根据矩形裁剪图片
    CGRect rect;
    rect.origin = CGPointMake(pixRect.xmin * scale, pixRect.ymin * scale);
    rect.size   = CGSizeMake((pixRect.xmax - pixRect.xmin)  * scale, (pixRect.ymax - pixRect.ymin) * scale);
    
    //获取dispRect中的数据
    CGImageRef sourceImageRef = [image CGImage];
    CGImageRef newImageRef    = CGImageCreateWithImageInRect( sourceImageRef, rect);
    UIImage *newImage = [UIImage imageWithCGImage:newImageRef];
    CGImageRelease(newImageRef);
    
    MGSRect pixDispRect;
    pixDispRect.xmin = width * (tempRect.xmin - dispRect.xmin)/(dispRect.xmax - dispRect.xmin);
    pixDispRect.xmax = width * (tempRect.xmax - dispRect.xmin)/(dispRect.xmax - dispRect.xmin);
    pixDispRect.ymin = height * (dispRect.ymax - tempRect.ymax)/(dispRect.ymax - dispRect.ymin);
    pixDispRect.ymax = height * (dispRect.ymax - tempRect.ymin)/(dispRect.ymax - dispRect.ymin);
    
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(width,  height), NO, 1);
    [newImage drawInRect:CGRectMake(pixDispRect.xmin, pixDispRect.ymin, pixDispRect.xmax - pixDispRect.xmin, pixDispRect.ymax - pixDispRect.ymin)];
    UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return UIImagePNGRepresentation(returnImage);
}

然后重写其他关键方法。

//地图浏览类型
-(MGSMapServerBrowseType)mapBrowseType
{
    return TypeMapVector;
}

//数据范围
-(MGSRect)entireExtent
{
    return self.dataRect;
}

//服务名称
-(NSString*)name
{
    return @"customVectorMapserver";
}

//有效性
-(bool)isValid
{
    return true;
}

//空间参考系
-(MGSSRefData*)SRS
{
    MGSSRefData *ref = [[MGSSRefData alloc]init];
    return ref;
}

2

数据准备

准备要制作自定义矢量地图服务的图片,以及确定需要展示的地图区域。

//构建图像
UIImage *image = [UIImage imageNamed:@"vectorServer.jpg"];
//显示的坐标范围
MGSRect customRect=MGSRectMake(12719586.11,3572675.61,12728907.50,3581903.60);

3

地图显示

实例化自定义矢量地图服务,设置信息。然后构建服务图层,最后进行显示。

//创建自定义矢量地图服务对象,并设置数据、地图范围
CustomVectorMapServer *myVectMapServer = [[CustomVectorMapServer alloc]init];
[myVectMapServer setDataWithRect:customRect andImage:image];
//创建服务图层,设置地图服务
MGSServerLayer *myVectorServerLayer = [[MGSServerLayer alloc] init];
myVectorServerLayer.mapServer = myVectMapServer;
//添加到地图中
[self.mapView.map append:myVectorServerLayer];

显示效果:

自定义矢量地图服务.jpg

自定义瓦片地图服务

在实现自定义瓦片地图服务之前,先了解两个知识:

地图瓦片技术:将配置好的一定坐标范围的地图,按照若干比例尺(瓦片级别)和指定的图片尺寸,在服务器端切成若干行及列的正方形图片,以指定的格式保存成图像文件,按一定的命名规则和组织方式存储到服务器的目录系统中或者数据系统中形成金字塔模型的静态图片

瓦片金字塔从底层到顶层,分辨率越来越高,但表示地理范围不变,一般根据地图服务平台的要求的最大缩放级别,把缩放级别最低。地图比例尺最大的地图图片作为金字塔的底层。

MapGIS Mobile支持由用户自定义切片规则,指定地图原点,地图范围,自定义缩放级别,并可以规定只让用户看到部分缩放级别。

1

自定义瓦片地图服务类

自定义瓦片地图服务类CustomTileMapServer,继承MGSTileMapServer,重新方法。

(1)根据entireExtent属性给定地图数据范围,也即是传递的自定义的地图范围。

-(MGSRect)entireExtent
{
    return self.dataRect;
}

(2)根据属性minZoom设置最小缩放级别、maxZoom设置最大缩放级别、minZoomCapacity、maxZoomCapacity设置服务数据源的最小最大缩放级别。

-(long)minZoom
{
    return 0;
}

-(long)maxZoom
{
    return 2;
}

-(long)minZoomCapacity
{
    return 0;
}

-(long)maxZoomCapacity
{
    return 2;
}

(3)重写getTileResolutionAtZoom,这个方法用于获得缩放级对应的瓦片分辨率:

-(double)getTileResolutionAtZoom:(long)zoom
{
    NSMutableArray *resolutions = [[NSMutableArray alloc]init];
    resolutions[0] = [NSNumber numberWithDouble:(aRect.xmax - aRect.xmin)/image.size.width/2];
    resolutions[1] = [NSNumber numberWithDouble:([resolutions[0] doubleValue] / 2)];
    resolutions[2] = [NSNumber numberWithDouble:([resolutions[1] doubleValue] / 2)];
    return [resolutions[zoom] doubleValue];
}

(4)重写tileOrigin方法,给定瓦片原点dotOrigin,在这里地图原点设为需要展示地图区域的左上角:

-(MGSDot)tileOrigin
{
    dotOrigin.x = aRect.xmin;
    dotOrigin.y = aRect.ymin;
    return self.dotOrigin;
}

(5)设置瓦片大小,一般256显示效果较好;

-(long)tileWidth
{
    return 256;
}

-(long)tileHeight
{
    return 256;
}

(6)重写getTileMatrixAtZoom:,传递每个级别下其实行列号,计算行列号代码如下所示:

/*!
 *  @brief	获得缩放级别对应的有效瓦片矩阵行列值
 *
 *  @discussion 获取比例尺=图上距离/实地距离,图上距离通过瓦片像素反算成米,实地距离通过地球半径与纬度计算
 *  @param 	zoom	瓦片缩放级
 *  @return	上(起始)行号,左(起始)列号,下(终止)行号,右(终止)列号
 */
-(MGSLRect)getTileMatrixAtZoom:(long)zoom
{
    double dXRes = [_resolutions[zoom] doubleValue] * _tileSize;
    double dYRes = [_resolutions[zoom] doubleValue] * _tileSize;
    
    MGSRect gridRect;
    gridRect.xmin = _dotOrigin.x + floor((_dataRect.xmin - _dotOrigin.x) / dXRes + 0.001) * dXRes;
    gridRect.xmax = _dotOrigin.x + ceil ((_dataRect.xmax - _dotOrigin.x) / dXRes - 0.001) * dXRes;
    gridRect.ymin = _dotOrigin.y - ceil ((_dotOrigin.y - _dataRect.ymin) / dYRes - 0.001) * dYRes;
    gridRect.ymax = _dotOrigin.y - floor((_dotOrigin.y - _dataRect.ymax) / dYRes + 0.001) * dYRes;
    
    MGSLRect rect;
    rect.top    = ceil(( _dotOrigin.y - gridRect.ymax) / dYRes - 0.001);
    rect.bottom = ceil(( _dotOrigin.y - gridRect.ymin) / dYRes - 0.001) - 1;
    rect.left   = ceil((gridRect.xmin - _dotOrigin.x ) / dXRes - 0.001);
    rect.right  = ceil((gridRect.xmax - _dotOrigin.x ) / dXRes - 0.001) - 1;
    
    myRect[(int)zoom] = rect;
    NSLog(@"getTileMatrix==> zoom = %d,top = %d,bottom = %d,left = %d,right = %d",(int)zoom,(int)rect.top,(int)rect.bottom,(int)rect.left,(int)rect.right);
    return rect;
}

(7)重写getTileImageWithRow:col:zoom:,根据上一步计算出来的行列号,在这里反算出行列号对应的地图范围,图片的地理范围包含在其中,然后根据地理范围跟dataRect的比值,计算图片尺寸,并返回当前瓦片二进制,代码如下所示:

/*!
 *  @brief	根据行列号、缩放级获取瓦片图像
 *
 *  @discussion 调用完该函数,需使用FreeTileImage释放buf
 *
 *  @param 	row     行号
 *  @param 	col     列号
 *  @param 	zoom	瓦片缩放级
 *  @return	返回buf字节数,失败返回0
 */
-(NSData*)getTileImageWithRow:(long) row col:(long) col zoom:(long) zoom
{
    CGSize size = [self.myImage size];
    
    //放大图片
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width*pow(2,zoom + 1), size.height*pow(2,zoom + 1)), NO, 1);
    [self.myImage drawInRect:CGRectMake(0, 0, size.width*pow(2,zoom + 1), size.height*pow(2,zoom + 1))];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    NSLog(@"image: width = %d,height = %d",(int)image.size.width,(int)image.size.height);
    
    CGSize clipSize = CGSizeMake(256, 256);
    //裁剪图片
    UIGraphicsBeginImageContextWithOptions(clipSize, NO, 1);
    //zoom等于1时,右下角图片从(-128,-128)开始绘制
    CGPoint point = CGPointMake((col - myRect[(int)zoom].left) * -clipSize.width, (row - myRect[(int)zoom].top) * -clipSize.height);
    NSLog(@"point : %d, %d",(int)point.x,(int)point.y);
    [image drawAtPoint:point];
    UIImage *clipImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return UIImagePNGRepresentation(clipImage);
}

2

数据准备

准备要制作自定义瓦片地图服务的图片,以及确定需要展示的地图区域。

//图像
UIImage *image = [UIImage imageNamed:@"tileServer.png"];
//瓦片地图范围
MGSRect customRect=MGSRectMake(12727405.02034,3569836.65592, 12741743.19188,3579555.67778);

3

实例化自定义地图服务对象

实例化自定义瓦片地图服务对象,设置信息。然后构建服务图层,最后进行显示。

//创建自定义瓦片地图服务对象,并设置数据、地图范围
CustomTileMapServer *myTileMapServer = [[CustomTileMapServer alloc] init];
[myTileMapServer setDataWithRect:customRect andImage:image];
//构建服务图层对象
MGSServerLayer *myTileServerLayer = [[MGSServerLayer alloc] init];
myTileServerLayer.mapServer = myTileMapServer;
//添加到地图中
[self.mapView.map append:myTileServerLayer];

显示效果:

自定义瓦片地图服务.jpg